package com.uni.crawl.service.impl.crawl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.toolkit.MPJWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.uni.crawl.common.enums.crawl.CrawlApprovalStatusEnum;
import com.uni.crawl.common.enums.crawl.CrawlPrefixEnum;
import com.uni.crawl.common.enums.crawl.CrawlTemplateBuildInParamEnum;
import com.uni.crawl.common.enums.crawl.CrawlTemplateStatusEnum;
import com.uni.crawl.common.util.CodeUtil;
import com.uni.crawl.common.util.crawl.*;
import com.uni.crawl.mapper.CrawlTemplateMapper;
import com.uni.crawl.model.entity.crawl.CrawlNamespace;
import com.uni.crawl.model.entity.crawl.CrawlTemplate;
import com.uni.crawl.model.form.crawl.CrawlTemplateForm;
import com.uni.crawl.model.form.crawl.CrawlTemplateMockForm;
import com.uni.crawl.model.query.crawl.CrawlTemplatePageQuery;
import com.uni.crawl.model.vo.crawl.CrawlTemplateMockRunVO;
import com.uni.crawl.model.vo.crawl.CrawlTemplateVO;
import com.uni.crawl.service.PageQueryUtils;
import com.uni.crawl.service.crawl.CrawlTemplateService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Objects;

/**
 * <p>
 * 爬虫模板 服务实现类
 * </p>
 *
 * @author JervisPG
 * @since 2023-12-18
 */
@Service
@AllArgsConstructor
public class CrawlTemplateServiceImpl extends MPJBaseServiceImpl<CrawlTemplateMapper, CrawlTemplate> implements CrawlTemplateService {

    private final RestTemplate restTemplate;

    @Override
    public boolean add(CrawlTemplateForm form) {
        CrawlTemplate tmpl = BeanUtil.copyProperties(form, CrawlTemplate.class);
        tmpl.setTmplCode(CodeUtil.genCode(CrawlPrefixEnum.TMPL));
        tmpl.setApproveStatus(CrawlApprovalStatusEnum.OK.getStatus());
        return save(tmpl);
    }

    @Override
    public boolean delete(Long id) {
        LambdaUpdateWrapper<CrawlTemplate> wp = Wrappers.lambdaUpdate();
        wp.eq(CrawlTemplate::getId, id);

        wp.set(CrawlTemplate::getIsDeleted, true);
        return update(wp);
    }

    @Override
    public boolean switchStatus(Long id) {
        LambdaUpdateWrapper<CrawlTemplate> wp = Wrappers.lambdaUpdate();
        wp.eq(CrawlTemplate::getId, id);

        wp.setSql("tmpl_status = !tmpl_status");
        return update(wp);
    }

    @Override
    public boolean update(Long id, CrawlTemplateForm form) {
        CrawlTemplate tmpl = getById(id);
        if (tmpl == null) {
            return false;
        }

        BeanUtil.copyProperties(form, tmpl);
        return updateById(tmpl);
    }

    @Override
    public CrawlTemplateForm getForm(Long id) {
        CrawlTemplate tmpl = getById(id);
        return BeanUtil.copyProperties(tmpl, CrawlTemplateForm.class);
    }

    @Override
    public CrawlTemplate getByTmplCode(String tmplCode) {
        LambdaQueryWrapper<CrawlTemplate> wp = Wrappers.lambdaQuery();
        wp.eq(CrawlTemplate::getTmplCode, tmplCode);
        return getOne(wp);
    }

    @Override
    public Page<CrawlTemplateVO> getPage(CrawlTemplatePageQuery pageQuery) {
        String nsCode = pageQuery.getNsCode();
        String tmplCode = pageQuery.getTmplCode();

        MPJLambdaWrapper<CrawlTemplate> wp = MPJWrappers.lambdaJoin();
        wp.selectAll(CrawlTemplate.class);
        wp.select(CrawlNamespace::getNsName);

        wp.leftJoin(CrawlNamespace.class, CrawlNamespace::getNsCode, CrawlTemplate::getNsCode);

        wp.eq(StrUtil.isNotEmpty(nsCode), CrawlTemplate::getNsCode, nsCode);
        wp.eq(StrUtil.isNotEmpty(tmplCode), CrawlTemplate::getTmplCode, tmplCode);
        wp.eq(Objects.nonNull(pageQuery.getTmplStatus()), CrawlTemplate::getTmplStatus, pageQuery.getTmplStatus());
        wp.eq(CrawlTemplate::getIsDeleted, false);
        PageQueryUtils.wrapTimeRange(wp, CrawlTemplate::getCreateTime, pageQuery);

        wp.orderByDesc(CrawlTemplate::getId);

        return selectJoinListPage(PageQueryUtils.getPage(pageQuery), CrawlTemplateVO.class, wp);
    }

    @Override
    public List<CrawlTemplate> listScheduleTemplate() {
        LambdaQueryWrapper<CrawlTemplate> wp = Wrappers.lambdaQuery();
        wp.eq(CrawlTemplate::getTmplStatus, CrawlTemplateStatusEnum.OPEN.getStatus());
        wp.ge(CrawlTemplate::getSchedHour, 1);
        return list(wp);
    }

    @Override
    public CrawlTemplateMockRunVO mockRun(CrawlTemplateMockForm form) throws Exception {
        CrawlTemplateMockRunVO vo = new CrawlTemplateMockRunVO();

        CrawlTemplate tmpl = getById(form.getId());
        if (tmpl == null) {
            vo.setExceptionMockResult("模板不存在");
            return vo;
        }

        // 优先用mock传入的seedUrl
        String seedUrl = StrUtil.isEmpty(form.getSeedUrl()) ? tmpl.getSeedUrl() : form.getSeedUrl();
        String pageNumWrapper = "{" + CrawlTemplateBuildInParamEnum.PAGE_NUM.getParam() + "}";
        String html;

        HttpHeaders httpHeaders = new HttpHeaders();
        populateHeaders(form, httpHeaders);

        HttpEntity<Void> httpEntity = new HttpEntity<>(null, httpHeaders);
        if (StrUtil.contains(seedUrl, pageNumWrapper)) {
            html = restTemplate.exchange(StrUtil.replace(seedUrl, pageNumWrapper, "1"), HttpMethod.GET, httpEntity, String.class).getBody();
        } else {
            html = restTemplate.exchange(seedUrl, HttpMethod.GET, httpEntity, String.class).getBody();
        }

        if (BooleanUtil.isTrue(form.getPageable())) {
            XsltTemplatePipline pagingSelectorPipline = XsltTemplatePiplineBuilder.create().seedUrl(seedUrl).tmpl(tmpl.getPagingSelectorTmpl()).build();
            vo.setPagingSelectorMockResult(XsltTemplatePiplineProcessor.process(pagingSelectorPipline, html));

            if (StrUtil.isNotEmpty(tmpl.getPagingListTmpl())) {
                XsltTemplatePipline pagingListPipline = XsltTemplatePiplineBuilder.create().seedUrl(seedUrl).tmpl(tmpl.getPagingListTmpl()).build();
                vo.setPagingListMockResult(XsltTemplatePiplineProcessor.process(pagingListPipline, html));
            } else {
                List<String> seedUrlParams = UrlUtils.getParamList(seedUrl);
                for (String seedUrlParam : seedUrlParams) {
                    if (CrawlTemplateBuildInParamEnum.PAGE_NUM.getParam().equals(seedUrlParam)) {
                        JSONArray pagingArray = new JSONArray();
                        for (int i = 1; i <= tmpl.getPagingLimit(); i++) {
                            JSONObject pagingObject = new JSONObject();
                            pagingObject.set(CrawlTemplateBuildInParamEnum.PAGE_NUM.getParam(), i);
                            pagingObject.set(CrawlTemplateBuildInParamEnum.PAGE_URL.getParam(), UrlUtils.replaceParam(seedUrl, pagingObject));
                            pagingArray.add(pagingObject);
                        }
                        vo.setPagingListMockResult(pagingArray);
                    }
                }
            }
        } else {
            XsltTemplatePipline pageDetailPipline = XsltTemplatePiplineBuilder.create().seedUrl(seedUrl).tmpl(tmpl.getPageDetailTmpl()).build();
            vo.setPageDetailMockResult(XsltTemplatePiplineProcessor.process(pageDetailPipline, html));
        }

        return vo;
    }

    private void populateHeaders(CrawlTemplateMockForm form, HttpHeaders httpHeaders) {
        List<String> headers = StrUtil.split(form.getHeaders(), StrUtil.LF);
        for (String header : headers) {
            int idx = StrUtil.indexOf(header, StrUtil.C_COLON);
            if (header.startsWith(StrUtil.COLON) || idx == 0) {
                continue;
            }

            String k = StrUtil.sub(header, 0, idx);
            String v = StrUtil.sub(header, idx + 1, header.length());
            if ("accept-encoding".equals(k)) {
                // 防止拷贝了gzip的header值出现压缩报文让程序不可读的问题
                continue;
            }
            httpHeaders.add(k, StrUtil.trim(v));
        }
    }

}
